package com.github.faucamp.simplertmp.amf;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * AMF object
 *
 * @author francois
 */
public class AmfObject implements AmfData {

  Map<String, AmfData> properties = new LinkedHashMap<String, AmfData>();
  protected int size = -1;
  /** Byte sequence that marks the end of an AMF object */
  protected static final byte[] OBJECT_END_MARKER = new byte[] { 0x00, 0x00, 0x09 };

  public AmfObject() {
  }

  public AmfData getProperty(String key) {
    return properties.get(key);
  }

  public void setProperty(String key, AmfData value) {
    properties.put(key, value);
  }

  public void setProperty(String key, boolean value) {
    properties.put(key, new AmfBoolean(value));
  }

  public void setProperty(String key, String value) {
    properties.put(key, new AmfString(value, false));
  }

  public void setProperty(String key, int value) {
    properties.put(key, new AmfNumber(value));
  }

  public void setProperty(String key, double value) {
    properties.put(key, new AmfNumber(value));
  }

  @Override
  public void writeTo(OutputStream out) throws IOException {
    // Begin the object
    out.write(AmfType.OBJECT.getValue());

    // Write key/value pairs in this object
    for (Map.Entry<String, AmfData> entry : properties.entrySet()) {
      // The key must be a STRING type, and thus the "type-definition" byte is implied (not included in message)
      AmfString.writeStringTo(out, entry.getKey(), true);
      entry.getValue().writeTo(out);
    }

    // End the object
    out.write(OBJECT_END_MARKER);
  }

  @Override
  public void readFrom(InputStream in) throws IOException {
    // Skip data type byte (we assume it's already read)
    size = 1;
    InputStream markInputStream = in.markSupported() ? in : new BufferedInputStream(in);

    while (true) {
      // Look for the 3-byte object end marker [0x00 0x00 0x09]
      markInputStream.mark(3);
      byte[] endMarker = new byte[3];
      markInputStream.read(endMarker);

      if (endMarker[0] == OBJECT_END_MARKER[0]
          && endMarker[1] == OBJECT_END_MARKER[1]
          && endMarker[2] == OBJECT_END_MARKER[2]) {
        // End marker found
        size += 3;
        return;
      } else {
        // End marker not found; reset the stream to the marked position and read an AMF property
        markInputStream.reset();
        // Read the property key...
        String key = AmfString.readStringFrom(in, true);
        size += AmfString.sizeOf(key, true);
        // ...and the property value
        AmfData value = AmfDecoder.readFrom(markInputStream);
        size += value.getSize();
        properties.put(key, value);
      }
    }
  }

  @Override
  public int getSize() {
    if (size == -1) {
      size = 1; // object marker
      for (Map.Entry<String, AmfData> entry : properties.entrySet()) {
        size += AmfString.sizeOf(entry.getKey(), true);
        size += entry.getValue().getSize();
      }
      size += 3; // end of object marker
    }
    return size;
  }
}
